
#include "Obj4dV2.h"


int CObj4dV2::Init_Obj(int _num_vertices, int _num_polys, int _num_frames,int destroy)
{
	// this function does nothing more than allocate the memory for an OBJECT4DV2
	// based on the sent data, later we may want to create more robust initializers
	// but the problem is that we don't want to tie the initializer to anthing yet
	// in 99% of cases this all will be done by the call to load object
	// we just might need this function if we manually want to build an object???


	// first destroy the object if it exists
	if (destroy)
		Destroy();

	// allocate memory for vertex lists
	if (!(vlist_local = (CVertex4d*)malloc(sizeof(CVertex4d)*_num_vertices*_num_frames)))
		return -1;

	// clear data
	memset((void *)vlist_local,0,sizeof(CVertex4d)*_num_vertices*_num_frames);

	if (!(vlist_trans = (CVertex4d*)malloc(sizeof(CVertex4d)*_num_vertices*_num_frames)))
		return -1;

	// clear data
	memset((void *)vlist_trans,0,sizeof(CVertex4d)*_num_vertices*_num_frames);

	// number of texture coordinates always 3*number of polys
	if (!(tlist = (CPoint2d*)malloc(sizeof(CPoint2d)*_num_polys*3)))
		return -1;

	// clear data
	memset((void *)tlist,0,sizeof(CPoint2d)*_num_polys*3);


	// allocate memory for radii arrays
	if (!(avg_radius = (float *)malloc(sizeof(float)*_num_frames)))
		return -1;

	// clear data
	memset((void *)avg_radius,0,sizeof(float)*_num_frames);


	if (!(max_radius = (float *)malloc(sizeof(float)*_num_frames)))
		return -1;

	// clear data
	memset((void *)max_radius,0,sizeof(float)*_num_frames);

	// allocate memory for polygon list
	if (!(plist = (POLY4DV2_PTR)malloc(sizeof(POLY4DV2)*_num_polys)))
		return -1;

	// clear data
	memset((void *)plist,0,sizeof(POLY4DV2)*_num_polys);

	// alias head pointers
	head_vlist_local = vlist_local;
	head_vlist_trans = vlist_trans;

	// set some internal variables
	num_frames     = _num_frames;
	num_polys      = _num_polys;
	num_vertices   = _num_vertices;
	total_vertices = _num_vertices*_num_frames;

	// return success
	return 0;

} // end Init_Obj

int CObj4dV2::Destroy()
{
	// this function destroys the sent object, basically frees the memory
	// if any that has been allocated

	// local vertex list
	if (head_vlist_local)
		free(head_vlist_local);

	// transformed vertex list
	if (head_vlist_trans)
		free(head_vlist_trans);

	// texture coordinate list
	if (tlist)
		free(tlist);

	// polygon list
	if (plist)
		free(plist);

	// object radii arrays
	if (avg_radius)
		free(avg_radius);

	if (max_radius)
		free(max_radius);

	// now clear out object completely
	memset((void *)this, 0, sizeof(OBJECT4DV2));

	// return success
	return 0;

} // end Destroy


// setting the frame is so important that it should be a member function
int CObj4dV2::Set_Frame(int frame)
{
	// this functions sets the current frame in a multi frame object
	// if the object is not multiframed then the function has no effect

	// test if object is valid?
	if (!this)
		return -1;

	// test if its multiframe?
	if (!(this->attr & OBJECT4DV2_ATTR_MULTI_FRAME))
		return -1;

	// we have a valid object and its multiframe, vector pointers to frame data

	// check bounds on frame
	if (frame < 0 )
		frame = 0;
	else
		if (frame >= this->num_frames)
			frame = this->num_frames - 1;

	// set frame
	this->curr_frame = frame;

	// update pointers to point to "block" of vertices that represent this frame
	// the vertices from each frame are 1-1 and onto relative to the polygons that
	// make up the object, they are simply shifted, this we need to re-vector the 
	// pointers based on the head pointers

	this->vlist_local = &(this->head_vlist_local[frame*this->num_vertices]);
	this->vlist_trans = &(this->head_vlist_trans[frame*this->num_vertices]);

	// return success
	return 0;

} // end Set_Frame

float CObj4dV2::Calc_Radius()
{
	// this function computes the average and maximum radius for 
	// sent object and opdates the object data for the "current frame"
	// it's up to the caller to make sure Set_Frame() for this object
	// has been called to set the object up properly

	// reset incase there's any residue
	avg_radius[curr_frame] = 0;
	max_radius[curr_frame] = 0;

	// loop thru and compute radius
	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		// update the average and maximum radius
		float dist_to_vertex = 
			sqrt(vlist_local[vertex].x*vlist_local[vertex].x +
			vlist_local[vertex].y*vlist_local[vertex].y +
			vlist_local[vertex].z*vlist_local[vertex].z);

		// accumulate total radius
		avg_radius[curr_frame]+=dist_to_vertex;

		// update maximum radius   
		if (dist_to_vertex > max_radius[curr_frame])
			max_radius[curr_frame] = dist_to_vertex; 

	} // end for vertex

	// finallize average radius computation
	avg_radius[curr_frame]/=num_vertices;

	// return max radius of frame 0
	return(max_radius[0]);

} // end Calc_Radius

int CObj4dV2::Compute_Poly_Normals()
{
	// the normal of a polygon is commonly needed in a number 
	// of functions, however, to store a normal turns out to
	// be counterproductive in most cases since the transformation
	// to rotate the normal ends up taking as long as computing the
	// normal -- HOWEVER, if the normal must have unit length, then
	// pre-computing the length of the normal, and then in real-time
	// dividing by this save a length computation, so we get the 
	// best of both worlds... thus, this function computes the length
	// of a polygon's normal, but care must be taken, so that we compute
	// the length based on the EXACT same two vectors that all other 
	// functions will use when computing the normal
	// in most cases the functions of interest are the lighting functions
	// if we can pre-compute the normal length
	// for all these functions then that will save at least:
	// num_polys_per_frame * (time to compute length of vector)

	// the way we have written the engine, in all cases the normals 
	// during lighting are computed as u = v0->v1, and v = v1->v2
	// so as long as we follow that convention we are fine.
	// also, since the new OBJECT4DV2 format supports multiple frames
	// we must perform these calculations for EACH frame of the animation
	// since although the poly indices don't change, the vertice positions
	// do and thus, so do the normals!!!


	// iterate thru the poly list of the object and compute normals
	// each polygon
	for (int poly=0; poly < num_polys; poly++)
	{

		// extract vertex indices into master list, rember the polygons are 
		// NOT self contained, but based on the vertex list stored in the object
		// itself
		int vindex_0 = plist[poly].vert[0];
		int vindex_1 = plist[poly].vert[1];
		int vindex_2 = plist[poly].vert[2];

		// we need to compute the normal of this polygon face, and recall
		// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
		CVector4d n;
		Calc_Plane_n(vlist_local[ vindex_0 ].v, vlist_local[ vindex_0 ].v, vlist_local[ vindex_0 ].v, n);

		// compute length of normal accurately and store in poly nlength
		// +- epsilon later to fix over/underflows
		plist[poly].nlength = n.Length();
	} // end for poly

	// return success
	return 0;

} // end Compute_Poly_Normals

///////////////////////////////////////////////////////////////////////////////

int CObj4dV2::Compute_Vertex_Normals()
{
	// the vertex normals of each polygon are commonly needed in a number 
	// functions, most importantly lighting calculations for gouraud shading
	// however, we only need to compute the vertex normals for polygons that are
	// gouraud shader, so for every vertex we must determine the polygons that
	// share the vertex then compute the average normal, to determine if a polygon
	// contributes we look at the shading flags for the polygon


	// algorithm: we are going to scan the polygon list and for every polygon
	// that needs normals we are going to "accumulate" the surface normal into all
	// vertices that the polygon touches, and increment a counter to track how many
	// polys contribute to vertex, then when the scan is done the counts will be used
	// to average the accumulated values, so instead of an O(n^2) algorithm, we get a O(c*n)

	// this tracks the polygon indices that touch a particular vertex
	// the array is used to count the number of contributors to the vertex
	// so at the end of the process we can divide each "accumulated" normal
	// and average
	int polys_touch_vertex[OBJECT4DV2_MAX_VERTICES];
	memset((void *)polys_touch_vertex, 0, sizeof(int)*OBJECT4DV2_MAX_VERTICES);

	// iterate thru the poly list of the object, compute its normal, then add
	// each vertice that composes it to the "touching" vertex array
	// while accumulating the normal in the vertex normal array

	for (int poly=0; poly < num_polys; poly++)
	{
		//Write_Error("\nprocessing poly %d", poly);

		// test if this polygon needs vertex normals
		if (plist[poly].attr & POLY4DV1_ATTR_SHADE_MODE_GOURAUD)
		{
			// extract vertex indices into master list, rember the polygons are 
			// NOT self contained, but based on the vertex list stored in the object
			// itself
			int vindex_0 = plist[poly].vert[0];
			int vindex_1 = plist[poly].vert[1];
			int vindex_2 = plist[poly].vert[2];

			//Write_Error("\nTouches vertices: %d, %d, %d", vindex_0, vindex_1, vindex_2);

			// we need to compute the normal of this polygon face, and recall
			// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
			CVector4d n;
			Calc_Plane_n(vlist_local[ vindex_0 ].v, vlist_local[ vindex_0 ].v, vlist_local[ vindex_0 ].v, n);

			// update vertex array to flag this polygon as a contributor
			polys_touch_vertex[vindex_0]++;
			polys_touch_vertex[vindex_1]++;
			polys_touch_vertex[vindex_2]++;

			//Write_Error("\nPoly touch array v[%d] = %d,  v[%d] = %d,  v[%d] = %d", vindex_0, polys_touch_vertex[vindex_0],
			//	vindex_1, polys_touch_vertex[vindex_1],
			//	vindex_2, polys_touch_vertex[vindex_2]);

			// now accumulate the normal into the vertex normal itself
			// note, we do NOT normalize at this point since we want the length of the normal
			// to weight on the average, and since the length is in fact the area of the parallelogram
			// constructed by uxv, so we are taking the "influence" of the area into consideration
			vlist_local[vindex_0].n += n;
			vlist_local[vindex_1].n += n;
			vlist_local[vindex_2].n += n;
		} // end for poly

	} // end if needs vertex normals

	// now we are almost done, we have accumulated all the vertex normals, but need to average them
	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		// if this vertex has any contributors then it must need averaging, OR we could check
		// the shading hints flags, they should be one to one
		//Write_Error("\nProcessing vertex: %d, attr: %d, contributors: %d", vertex, 
		//	obj->vlist_local[vertex].attr, 
		//	polys_touch_vertex[vertex]);

		// test if this vertex has a normal and needs averaging
		if (polys_touch_vertex[vertex] >= 1)
		{
			vlist_local[vertex].nx/=polys_touch_vertex[vertex];
			vlist_local[vertex].ny/=polys_touch_vertex[vertex];
			vlist_local[vertex].nz/=polys_touch_vertex[vertex];

			// now normalize the normal
			vlist_local[vertex].n.Normalize();
	
			//Write_Error("\nAvg Vertex normal: [%f, %f, %f]", obj->vlist_local[vertex].nx,
			//	obj->vlist_local[vertex].ny,
			//	obj->vlist_local[vertex].nz);

		} // end if

	} // end for

	// return success
	return 0;

} // end Compute_Vertex_Normals